<template>
  <div
    class="file-uploader file-list-mode"
    :class="{ 'has-files': internalFileList && internalFileList.length > 0 }"
  >
    <van-uploader
      ref="uploaderRef"
      v-model="internalFileList"
      multiple
      :max-count="maxCount"
      :max-size="maxSizeBytes"
      :after-read="handleAfterRead"
      @oversize="onOversize"
      @delete="onDelete"
      @click-preview="onVanClickPreview"
      :show-upload="showUpload"
      :deletable="canDelete"
      :upload-text="uploadText"
      :accept="accept"
      :preview-image="true"
      upload-icon="add-o"
    >
      <!-- 上传按钮：使用 add-o 图标，置于右上角，保持所有上传功能不变 -->
      <template #upload>
        <button
          type="button"
          class="list-upload-btn"
          aria-label="添加附件"
          @click="openFilePicker"
        >
          <van-icon name="add-o" class="list-upload-btn__icon" />
        </button>
        <!-- 原生隐藏文件选择器（不 display:none，确保 iOS/微信可触发） -->
        <input
          ref="nativeInputRef"
          class="native-file-input"
          type="file"
          :accept="accept || '*/*'"
          :multiple="true"
          @change="onNativeChange"
        />
      </template>

      <!-- 使用 preview-cover 渲染列表信息，但保留 Vant 默认预览点击（点缩略图仍然打开预览） -->
      <template #preview-cover="{ file, index }">
        <!-- 左侧缩略图：强制依据 isImage/url 覆盖默认图标，避免 Vant 误判导致图片显示为文件图标 -->
        <div
          class="file-row-cover"
          @click.stop="isReadonly ? previewDirect(internalFileList[index] || file) : openMore(internalFileList[index] || file, index)"
          @touchstart.stop
        >
          <div class="file-row__thumb" v-if="(internalFileList[index] || file)?.isImage && ((internalFileList[index] || file)?.url || (internalFileList[index] || file)?.content)">
            <img
              :src="(internalFileList[index] || file)?.url || (internalFileList[index] || file)?.content"
              alt="缩略图"
              class="file-row__thumb-img"
              @error="onThumbError($event, internalFileList[index] || file, index)"
              @click.stop="isReadonly ? previewDirect(internalFileList[index] || file) : openMore(internalFileList[index] || file, index)"
              @touchstart.stop
            />
          </div>
          <div v-else class="file-row__left-gap"></div>
          <div class="file-row__info">
            <div class="file-row__name">{{ displayName(internalFileList[index] || file) }}</div>
            <div class="file-row__size">{{ formatSize((internalFileList[index] || file)?.file?.size) }}</div>
          </div>
          <div class="file-row__actions" v-if="canDelete">
            <van-icon
              name="ellipsis"
              class="file-row__more"
              @click.stop="openMore(internalFileList[index] || file, index)"
              @touchstart.stop
            />
            <van-icon
              name="cross"
              class="file-row__remove"
              @click.stop="doRemoveIndex(index)"
              @touchstart.stop
            />
          </div>
        </div>
      </template>
    </van-uploader>

    <!-- 更多操作面板 -->
    <van-action-sheet
      v-if="!isReadonly"
      v-model:show="moreSheetVisible"
      title="附件操作"
      cancel-text="取消"
    >
      <van-cell-group inset>
        <van-cell
          :border="false"
          title="预览"
          icon="eye-o"
          @click="doPreview()"
        />
        <!-- <van-cell title="重命名" icon="edit" is-link @click="openRename(currentActionFile)" /> -->
        <van-cell
          v-if="canDelete"
          :border="false"
          title="移除"
          icon="delete-o"
          class="danger-cell"
          @click="doRemoveIndex(currentActionIndex)"
        />
      </van-cell-group>
    </van-action-sheet>

    <!-- 重命名对话框 -->
    <van-dialog
      v-model:show="renameDialog.visible"
      title="重命名"
      show-cancel-button
      @confirm="confirmRename"
      @cancel="renameDialog.visible = false"
    >
      <div style="padding: 12px 16px">
        <van-field
          v-model="renameDialog.value"
          clearable
          autofocus
          placeholder="输入新的文件名"
        />
      </div>
    </van-dialog>

    <!-- 关闭外部自定义列表，避免与 Vant 默认预览重复；若需保留可改为 true -->
    <div
      v-if="false && internalFileList && internalFileList.length"
      class="file-list"
    >
      <!-- 原自定义列表内容保留但不显示 -->
    </div>
  </div>
</template>

<script setup>
import { ref, watch, computed, nextTick } from "vue";
import { showToast, showImagePreview } from "vant";
import { uploadFile, refreshTokenApi } from "@/api/index";
import { request, API_BASE_URL } from "@/utils/http";
import { storage } from "@/utils/storage";
import { useRouter } from "vue-router";

const props = defineProps({
  modelValue: { type: Array, default: () => [] },
  maxFileCount: { type: Number, default: 9 },
  maxFileSizeMB: { type: Number, default: 10 },
  previewSize: { type: [Number, String], default: 56 },
  showUploadButton: { type: Boolean, default: true },
  deletable: { type: Boolean, default: true },
  uploadText: { type: String, default: "" },
  accept: { type: String, default: "" },
  readonly: { type: Boolean, default: false },
});

const emit = defineEmits([
  "update:modelValue",
  "filesChanged",
  "uploaded",
  "deleted",
]);
const internalFileList = ref(props.modelValue);
const router = useRouter();

// 只读/显示控制
const isReadonly = computed(() => props.readonly === true);
const showUpload = computed(() => (props.showUploadButton && !isReadonly.value));
const canDelete = computed(() => (props.deletable && !isReadonly.value));

// 直接用原生 input[type=file]，提升一次点击的成功率（iOS/微信内置浏览器）
const uploaderRef = ref(null);
const nativeInputRef = ref(null);
let pickerLock = false; // 防抖锁，避免一次手势触发两次（touchstart + click）
const openFilePicker = async () => {
  try {
    if (pickerLock) return; // 已在打开流程中，忽略重复触发
    pickerLock = true;

    // 优先使用自建原生 input，避免 Vant 内部结构/样式影响
    if (nativeInputRef.value) {
      nativeInputRef.value.click();
      // 在大多数环境下，文件选择器会阻塞；使用定时器解锁，避免回到页面后再次触发
      setTimeout(() => { pickerLock = false; }, 800);
      return;
    }
    // 兜底：尝试触发 van-uploader 内部 input
    await nextTick();
    const root = uploaderRef.value?.$el || uploaderRef.value;
    const input = root?.querySelector('input[type="file"]');
    if (input) {
      input.click();
      setTimeout(() => { pickerLock = false; }, 800);
      return;
    }
    const uploadEl = root?.querySelector('.van-uploader__upload');
    uploadEl && uploadEl.click();
    setTimeout(() => { pickerLock = false; }, 800);
  } catch (e) {
    console.warn('打开文件选择器失败:', e);
    pickerLock = false;
  }
};

// 将内部打开文件选择器的方法暴露给父组件，便于父组件实现“整行点击即可上传”
defineExpose({ openFilePicker });

const onNativeChange = async (e) => {
  try {
    const fileList = Array.from(e?.target?.files || []);
    // 重置，保证可以选择同一文件
    if (e?.target) e.target.value = '';
    if (!fileList.length) return;

    // 数量限制处理
    const remain = Math.max(0, maxCount.value - internalFileList.value.length);
    if (remain <= 0) {
      showToast(`最多可上传${maxCount.value}个文件`);
      return;
    }
    const selected = fileList.slice(0, remain);

    // size 限制与过滤
    const oversize = selected.filter((f) => f.size > maxSizeBytes.value);
    if (oversize.length) {
      onOversize();
    }
    const validFiles = selected.filter((f) => f.size <= maxSizeBytes.value);
    if (!validFiles.length) return;

    // 构造 Vant 格式的条目并加入列表，然后复用 handleAfterRead 上传逻辑
    const items = validFiles.map((f) => ({ file: f, name: f.name }));
    internalFileList.value = [...internalFileList.value, ...items];
    await handleAfterRead(items);
  } catch (err) {
    console.warn('文件选择处理失败:', err);
  }
};

watch(
  () => props.modelValue,
  (val) => {
    // 过滤掉 null/undefined，避免预览插槽收到空条目导致运行时异常
    internalFileList.value = Array.isArray(val) ? [...val].filter(Boolean) : [];
  }
);

watch(
  internalFileList,
  async (list) => {
    let mutated = false;
    for (const item of list) {
      try {
        if (!item) continue;
        // 先根据扩展名/MIME 重算 isImage，避免初始为 false 的条目无法翻转为图片
        const prevIsImage = item.isImage;
        ensureIsImage(item);
        if (item.isImage !== prevIsImage) mutated = true;

        const hasPreview = item.url || item.content;
        const previewPath = item.uploadedPath || item.serverPath || item.path;

        // 基于扩展名或 MIME 猜测是否为图片
        const nameOrPath = (item.name || previewPath || "").split("?")[0].split("#")[0];
        const ext = nameOrPath.split(".").pop()?.toLowerCase();
        const imgExts = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
        const isLikelyImage = Boolean(
          (item.file && item.file.type && item.file.type.startsWith("image/")) ||
            (ext && imgExts.includes(ext)) ||
            item.isImage === true
        );

        if (!hasPreview && previewPath && isLikelyImage) {
          const direct = String(previewPath);
          const isAbsoluteUrl = /^(https?:|data:|blob:)/i.test(direct);
          const isRootRelative = /^\//.test(direct);
          if (isAbsoluteUrl) {
            // 直接使用可访问的绝对URL
            item.url = direct;
            const prev = item.isImage;
            ensureIsImage(item);
            if (item.isImage !== prev) mutated = true;
            mutated = true;
          } else if (isRootRelative) {
            // 将相对路径拼接为后端基地址的绝对URL（适用于静态可访问资源）
            item.url = `${API_BASE_URL}${direct}`;
            const prev = item.isImage;
            ensureIsImage(item);
            if (item.isImage !== prev) mutated = true;
            mutated = true;
          } else {
            // 相对路径：优先拼接为后端基地址的绝对URL，再尝试下载接口兜底（鉴权资源）
            const cleaned = direct.startsWith('/') ? direct : `/${direct}`;
            item.url = `${API_BASE_URL}${cleaned}`;
            const prev = item.isImage;
            ensureIsImage(item);
            if (item.isImage !== prev) mutated = true;
            mutated = true;
            try {
              const blobUrl = await fetchBlobUrlWithRefresh(previewPath);
              if (blobUrl) {
                item.url = blobUrl;
                const prev2 = item.isImage;
                ensureIsImage(item);
                if (item.isImage !== prev2) mutated = true;
                mutated = true;
              }
            } catch (e) {}
          }
        }
      } catch (e) {}
    }
    // 用新的数组引用触发 van-uploader 重新渲染缩略图类型（图片/文件）
    if (mutated) {
      internalFileList.value = [...internalFileList.value];
      syncToParent();
    }
  },
  { deep: true, immediate: true }
);

const maxCount = computed(() => props.maxFileCount ?? 9);
const maxSizeBytes = computed(() => (props.maxFileSizeMB ?? 10) * 1024 * 1024);
const onOversize = () => {
  showToast(`单个文件不能超过${props.maxFileSizeMB}MB`);
};

const syncToParent = () => {
  // 发出一个新的数组引用，确保父层 v-model 响应更新
  const next = [...internalFileList.value];
  emit("update:modelValue", next);
  const paths = next
    .map((f) => f && (f.uploadedPath || f.serverPath || f.path))
    .filter(Boolean)
    .join(",");
  emit("filesChanged", paths);
};

// (removed duplicate onDelete; use unified onDelete below)

// 右侧操作：更多面板 & 删除 & 预览 & 重命名
const moreSheetVisible = ref(false);
const currentActionFile = ref(null);
const currentActionIndex = ref(-1);

const openMore = (file, index) => {
  const fromList =
    typeof index === "number" ? internalFileList.value?.[index] : null;
  currentActionIndex.value =
    typeof index === "number"
      ? index
      : fromList
      ? internalFileList.value.indexOf(fromList)
      : -1;
  currentActionFile.value = fromList || file;
  moreSheetVisible.value = true;
};

const previewDirect = (file) => {
  currentActionFile.value = file;
  moreSheetVisible.value = false;
  doPreview();
};

const onVanClickPreview = (arg1, arg2) => {
  try {
    // 兼容 Vant: 可能是 (item, index) 或 ({ file, index })
    const file = arg1 && typeof arg1 === 'object' && 'file' in arg1 ? arg1.file : arg1;
    const index = arg1 && typeof arg1.index === 'number' ? arg1.index : (typeof arg2 === 'number' ? arg2 : -1);
    const target = typeof index === 'number' && index >= 0 ? (internalFileList.value[index] || file) : file;
    if (isReadonly.value) {
      previewDirect(target);
    } else {
      openMore(target, index);
    }
  } catch (e) {
    // 兜底：直接尝试预览当前文件
    if (isReadonly.value) {
      const fallback = (arg1 && arg1.file) ? arg1.file : arg1;
      previewDirect(fallback);
    }
  }
};

const findIndexByFile = (file) => {
  if (!file) return -1;
  return internalFileList.value.findIndex(
    (f) =>
      f === file ||
      (f?.serverPath && file?.serverPath && f.serverPath === file.serverPath) ||
      (f?.uploadedPath &&
        file?.uploadedPath &&
        f.uploadedPath === file.uploadedPath) ||
      (f?.path && file?.path && f.path === file.path) ||
      (f?.file?.name &&
        file?.file?.name &&
        f.file && file.file && // 添加安全检查，确保file对象存在
        f.file.name === file.file.name &&
        f.file?.size === file.file?.size)
  );
};

const onDeleteByPredicate = (file) => {
  const idx = findIndexByFile(file);
  if (idx !== -1) {
    const removed = internalFileList.value.splice(idx, 1)[0];
    syncToParent();
    emit("deleted", removed);
    return;
  }
  // 回退：按引用过滤
  internalFileList.value = internalFileList.value.filter((f) => f !== file);
  syncToParent();
  emit("deleted", file);
};

const onDelete = (item) => {
  onDeleteByPredicate(item);
};

const doRemoveIndex = (index) => {
  moreSheetVisible.value = false;
  const i = typeof index === "number" ? index : currentActionIndex.value;
  if (typeof i === "number" && i >= 0 && i < internalFileList.value.length) {
    const removed = internalFileList.value.splice(i, 1)[0];
    syncToParent();
    emit("deleted", removed);
    return;
  }
  // 若索引不可用，回退为按文件对象删除
  if (currentActionFile.value) {
    onDeleteByPredicate(currentActionFile.value);
  }
};

const doRemove = (file) => {
  moreSheetVisible.value = false;
  onDeleteByPredicate(file);
};

const normalizeServerPath = (raw) => {
  if (!raw || typeof raw !== "string") return "";
  try {
    // 仅处理 Windows 反斜杠，不再移除查询/锚点，避免破坏后端签名或参数
    return raw.replace(/\\/g, "/");
  } catch {
    return raw;
  }
};

// 统一通过接口获取Blob并生成本地URL；如遇到后端返回JSON提示401/token为空，则尝试刷新token后重试
const fetchBlobUrlWithRefresh = async (filePath) => {
  const makeRequest = async () => {
    const response = await request(`${API_BASE_URL}/OA/file/download`, {
      method: "GET",
      params: { path: filePath },
      responseType: "blob",
    });
    return response;
  };

  // 首次请求
  const resp = await makeRequest();
  const headers = resp?.headers || {};
  const contentType = headers["content-type"] || headers["Content-Type"] || "";
  const blobData = resp && resp.data ? resp.data : resp;

  // 如果后端以JSON返回（常见于业务code=401），解析后尝试刷新token并重试
  if (String(contentType).toLowerCase().includes("application/json")) {
    try {
      const text = await blobData.text();
      const json = JSON.parse(text);
      const msg = json?.msg || json?.message || "";
      if (json?.code === 401 || /token/.test(msg)) {
        try {
          const refreshResp = await refreshTokenApi();
          const newToken = refreshResp?.data?.accessToken;
          if (newToken) {
            storage.setItem("accessToken", newToken);
            const retried = await makeRequest();
            const retriedBlobData =
              retried && retried.data ? retried.data : retried;
            const blob =
              retriedBlobData instanceof Blob
                ? retriedBlobData
                : new Blob([retriedBlobData]);
            return window.URL.createObjectURL(blob);
          }
          throw new Error("刷新token失败，请重新登录");
        } catch (e) {
          throw new Error(json?.msg || "登录已过期，请重新登录");
        }
      }
      // 非401业务JSON，按普通文本处理为Blob
      const blob = new Blob([text], { type: "text/plain" });
      return window.URL.createObjectURL(blob);
    } catch (e) {
      // 如果解析失败，按普通Blob处理
      const blob = blobData instanceof Blob ? blobData : new Blob([blobData]);
      return window.URL.createObjectURL(blob);
    }
  }

  // 正常Blob返回
  const blob = blobData instanceof Blob ? blobData : new Blob([blobData]);
  return window.URL.createObjectURL(blob);
};

const doPreview = async () => {
  // 使用上传接口返回的路径进行预览
  moreSheetVisible.value = false;
  const item = currentActionFile.value;
  if (!item) {
    showToast("未选择附件");
    return;
  }
  const rawPath = item.uploadedPath || item.serverPath || item.path;
  if (!rawPath) {
    showToast("暂无可预览的地址（请先上传）");
    return;
  }
  const path = normalizeServerPath(rawPath);

  const lower = (path || "").toLowerCase();
  const ext = lower.split(".").pop();
  const imgExts = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
  const isImage = imgExts.includes(ext) || item.isImage === true;

  try {
    if (isImage) {
      const blobUrl = await fetchBlobUrlWithRefresh(path);
      if (!blobUrl) {
        showToast("预览地址获取失败");
        return;
      }
      showImagePreview([blobUrl]);
      return;
    }
    // 非图片：跳转到文件预览页（内嵌预览，类似钉钉）
    const name = displayName(item);
    router.push({
      path: "/file-preview",
      query: {
        path: encodeURIComponent(path),
        name: encodeURIComponent(name || ""),
      },
    });
  } catch (e) {
    console.error("预览失败:", e);
    const msg = e?.message || "";
    if (msg.includes("登录")) {
      showToast(msg);
      const currentPath = window.location.hash.slice(1);
      setTimeout(() => {
        window.location.href = `/#/login?redirect=${encodeURIComponent(
          currentPath || "/"
        )}`;
      }, 1200);
    } else if (/401/.test(msg) || /token/.test(msg)) {
      showToast("登录已过期，请重新登录");
      const currentPath = window.location.hash.slice(1);
      setTimeout(() => {
        window.location.href = `/#/login?redirect=${encodeURIComponent(
          currentPath || "/"
        )}`;
      }, 1200);
    } else {
      showToast("预览失败，请稍后重试");
    }
  }
};

const renameDialog = ref({ visible: false, value: "", file: null });

// const openRename = (file) => {
//   renameDialog.value.visible = true;
//   renameDialog.value.file = file;
//   renameDialog.value.value = displayName(file);
//   moreSheetVisible.value = false;
// };

const confirmRename = () => {
  const val = (renameDialog.value.value || "").trim();
  if (!val) {
    showToast("文件名不能为空");
    return;
  }
  const file = renameDialog.value.file;
  if (file) {
    // 若原始名称有扩展名且用户未输入扩展名，则保留原扩展名
    const orig = displayName(file);
    const origExt = (() => {
      const idx = orig.lastIndexOf(".");
      return idx > 0 ? orig.slice(idx + 1) : "";
    })();
    const hasExt = val.includes(".");
    file.name = hasExt || !origExt ? val : `${val}.${origExt}`;
    // 触发响应更新
    internalFileList.value = [...internalFileList.value];
    syncToParent();
  }
  renameDialog.value.visible = false;
};
const markItem = (item, patch) => {
  Object.assign(item, patch);
};

const ensureIsImage = (item) => {
  if (!item) return;
  const source =
    item.name || item.uploadedPath || item.serverPath || item.path || item.url || "";
  const withoutQuery = source.split("?")[0].split("#")[0];
  const ext = withoutQuery.split(".").pop()?.toLowerCase();
  const imgExts = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];

  let isImg = false;
  if (item.file && item.file.type && item.file.type.startsWith("image/")) {
    isImg = true;
  } else if (ext && imgExts.includes(ext)) {
    isImg = true;
  }
  item.isImage = isImg;
};

const pickPathFromString = (s) => {
  if (typeof s !== "string") return undefined;
  const str = s.trim();
  if (str.startsWith("http://") || str.startsWith("https://")) return str;
  if (str.startsWith("/")) return str;
  const match = str.match(
    /(\/(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+(?:\.[A-Za-z0-9._-]+)?)/
  );
  return match ? match[1] : undefined;
};

const extractServerPath = (res) => {
  try {
    if (typeof res === "string") return pickPathFromString(res) ?? res;
    const payload = res?.data !== undefined ? res.data : res;
    if (typeof payload === "string")
      return pickPathFromString(payload) ?? payload;
    if (Array.isArray(payload)) {
      const first =
        payload.find((x) => typeof x === "string") ||
        payload.find((x) => x && (x.path || x.url));
      if (typeof first === "string") return first;
      if (first?.path) return first.path;
      if (first?.url) return first.url;
    }
    if (payload && typeof payload === "object") {
      if (payload.path) return payload.path;
      if (payload.url) return payload.url;
      if (Array.isArray(payload.paths) && payload.paths.length)
        return payload.paths[0];
      if (Array.isArray(payload.urls) && payload.urls.length)
        return payload.urls[0];
    }
    if (res?.path) return res.path;
    if (res?.url) return res.url;
    if (typeof res?.msg === "string") {
      const p = pickPathFromString(res.msg);
      if (p) return p;
    }
  } catch (e) {}
  return undefined;
};

const doUpload = async (item) => {
  try {
    if (!item) {
      throw new Error("文件对象为空");
    }
    markItem(item, { status: "uploading", message: "上传中..." });
    if (!item.file) throw new Error("无文件数据");
    const res = await uploadFile(item.file);
    console.log("上传接口返回:", res);

    const path = extractServerPath(res);
    if (!path) throw new Error("未能解析上传返回的文件路径");
    markItem(item, {
      status: "done",
      message: "已上传",
      serverPath: path,
      uploadedPath: path,
    });
    ensureIsImage(item);
    emit("uploaded", item);
    syncToParent();
  } catch (e) {
    console.error("上传失败:", e);
    if (item) {
      markItem(item, { status: "failed", message: e?.message || "上传失败" });
    }
    showToast(e?.message || "上传失败，请重试");
  }
};

const handleAfterRead = async (fileOrFiles) => {
  const items = Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles];
  items.forEach((raw) => {
    if (!raw) return; // 跳过空值
    markItem(raw, {
      name: raw.file?.name || raw.name,
      isImage: raw.file?.type?.startsWith("image/"),
      status: "uploading",
      message: "上传中...",
    });
    ensureIsImage(raw);
  });
  for (const raw of items) {
    if (raw) { // 确保raw不为空
      await doUpload(raw);
    }
  }
};

const formatSize = (size) => {
  if (!size || typeof size !== "number") return "";
  if (size < 1024) return `${size}B`;
  const kb = size / 1024;
  if (kb < 1024) return `${kb.toFixed(2)}KB`;
  const mb = kb / 1024;
  return `${mb.toFixed(2)}MB`;
};

const displayName = (file) => {
  // 添加安全检查，确保file对象存在
  if (!file) return "未命名附件";
  // 安全访问file.file属性，避免null.file错误
  const n = file?.name || (file?.file && file.file?.name);
  if (n && typeof n === "string" && n.trim().length) return n;
  const src = file?.uploadedPath || file?.serverPath || file?.path || file?.url || "";
  if (!src) return "未命名附件";
  try {
    const withoutQuery = src.split("?")[0].split("#")[0];
    const parts = withoutQuery.split("/");
    const last = parts[parts.length - 1];
    return (last && last.trim().length) ? last : "未命名附件";
  } catch {
    return "未命名附件";
  }
};
const onThumbError = async (evt, file, index) => {
  try {
    const target = internalFileList.value?.[index] || file;
    const previewPath = target?.uploadedPath || target?.serverPath || target?.path;
    if (!previewPath) return;
    const normalized = normalizeServerPath(previewPath);
    const blobUrl = await fetchBlobUrlWithRefresh(normalized);
    if (blobUrl) {
      target.url = blobUrl;
      ensureIsImage(target);
      internalFileList.value = [...internalFileList.value];
    }
  } catch (e) {
    console.warn('缩略图回退失败:', e);
  }
};
</script>
<style scoped>
.file-uploader {
  position: relative;
}

/* 调整：去掉顶部预留，让加号与“合同附件”文字同一行（通过上移定位） */
.file-list-mode {
  position: relative;
  padding-top: 0;
}
.file-list-mode.has-files {
  padding-top: 0;
}

/* 让 van-uploader 宽度占满并取消自身定位，使上传按钮以外层容器为参照 */
::v-deep(.van-uploader) {
  position: static !important;
  display: block;
  width: 100%;
}
/* —— 列表化展示（保留点击缩略图开启 Vant 预览） —— */
/* 包裹容器改为块级，让预览项纵向排列 */
::v-deep(.van-uploader__wrapper) {
  display: block !important;
  width: 100% !important;
  position: static !important;
}

/* 单个预览项占满一行：相对定位，便于将信息区绝对定位到缩略图右侧 */
::v-deep(.van-uploader__preview) {
  position: relative;
  display: block;
  width: 100% !important;
  min-height: 64px; /* 适配28px大字号 */
  padding: 10px 0;
  box-sizing: border-box;
  border-radius: 8px;
  background-color: #fff;
  margin: 8px 0 0 0; /* 顶部间距，去掉默认右间距 */
  overflow: visible; /* 防止内部 cover 超出时被裁剪 */
}

/* 左：缩略图（图片/文件统一 40x40） */
::v-deep(.van-uploader__preview-image) {
  position: static !important; /* 让 cover 的绝对定位以 .van-uploader__preview 为参照，并避免被 40x40 容器裁剪 */
  position: static !important;
  overflow: visible !important;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  background: transparent !important; /* 去掉默认灰色块背景 */
}
/* 调整图片元素本身的展示（object-fit 应用于 img 而不是容器） */
::v-deep(.van-image__img) {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 8px;
  /* 隐藏默认图片内容，仅显示我们在 preview-cover 中自定义的缩略图 */
  opacity: 0 !important;
  pointer-events: none !important;
}
::v-deep(.van-uploader__file) {
  /* 让 cover 以 .van-uploader__preview 为参照，并避免被 40x40 容器裁剪；去掉默认灰背景 */
  position: static !important;
  overflow: visible !important;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  background: transparent !important;
  margin-left: 2px !important; /* 与图片缩略图左对齐 */
}
::v-deep(.van-uploader__file .van-icon) {
  display: inline-block !important;
  font-size: 32px; /* 统一图标大小与缩略图比例 */
  width: 40px;
  height: 40px;
  line-height: 40px;
}
::v-deep(.van-uploader__file-name) {
  display: none !important;
}

/* 统一图片与文件容器的左边距，保持左对齐 */
::v-deep(.van-uploader__preview-image) {
  margin-left: 0 !important;
}

/* 隐藏右上角的原生删除按钮（仅隐藏阴影样式的） */
::v-deep(.van-uploader__preview-delete--shadow) {
  display: none !important;
}

/* 覆盖层扩展至整行，内部自行布局左缩略图 + 右文本信息 */
::v-deep(.van-uploader__preview-cover) {
  position: absolute !important;
  left: 0px; /* 与预览项内边距保持一致，确保内容整体左对齐 */
  right: 12px;
  top: 0px; /* 微调让图片与文字对齐 */
  transform: none; /* 取消居中位移 */
  display: flex; /* 左右布局 */
  align-items: center;
  min-width: 0;
  pointer-events: auto; /* 开启点击，整行点击可打开操作弹窗 */
  z-index: 3; /* 确保覆盖在图片之上，避免被图片遮挡导致名称与按钮不显示 */
  cursor: pointer;
}

/* 行容器：用于整行点击打开动作面板 */
.file-row-cover {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

/* 左侧自定义缩略图 */
.file-row__thumb {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  background: #f2f3f5;
  margin-right: 16px;
}
.file-row__thumb-img {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 8px;
  display: block;
}
.file-row__thumb-file {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  background: #f2f3f5;
}
.file-row__left-gap {
  width: 40px;
  height: 40px;
  margin-right: 16px;
}

/* 右侧操作区可点击 */
.file-row__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  pointer-events: auto;
}
.file-row__more {
  color: #c8c9cc;
}
.file-row__remove {
  color: #c8c9cc;
}
/* 中间文本区 */
.file-row__info {
  flex: 1;
  min-width: 0;
  pointer-events: auto;
}
.file-row__name {
  font-size: 30px;
  color: #333333;
  line-height: 36px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-row__size {
  font-size: 12px;
  color: #969799;
  line-height: 20px;
}

/* 预览状态/进度图标位置微调，避免跑到行外 */
::v-deep(.van-uploader__preview-status) {
  left: 8px !important;
  top: 8px !important;
  right: auto;
  bottom: auto;
  transform: none !important;
}

/* 右上角加号按钮：与“合同附件”文字平齐并右对齐 */
::v-deep(.van-uploader__upload) {
  position: absolute;
  right: 8px;
  left: auto !important;
  top: -36px;
  width: 40px; /* 扩大点击区域 */
  height: 40px; /* 扩大点击区域 */
  background-color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.list-upload-btn {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.list-upload-btn__icon {
  font-size: 32px !important;
  font-weight: 800 !important;
}

/* Fallback for default Vant upload icon when slot isn't applied */
::v-deep(.van-uploader__upload-icon) {
  font-size: 50px !important;
  font-weight: 800 !important;
}

/* Also target any icon inside the upload area to cover different Vant versions */
::v-deep(.van-uploader__upload .van-icon) {
  font-size: 50px !important;
  font-weight: 800 !important;
}

/* 文本信息区（来自 preview-cover 插槽） */
.file-row__name {
  font-size: 30px;
  color: #333333;
  line-height: 36px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-row__size {
  font-size: 12px;
  color: #969799;
  line-height: 20px;
}
.file-row__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
.file-row__more {
  color: #c8c9cc;
}
.file-row__remove {
  color: #c8c9cc;
}

/* 原生隐藏文件输入：不可见但不 display:none，确保 iOS/微信允许弹窗 */
.native-file-input {
  position: fixed;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0.001; /* 非零透明 */
}
</style>